package game.server;

import game.controller.Region.Region;
import game.controller.Unit.*;
import lobby.packet.OpCode;
import lobby.packet.StartGamePacket;
import lobby.packet.JoinLobbyPacket;
import lobby.controller.User;

import java.io.IOException;
import java.net.InetAddress;
import java.net.DatagramSocket;
import java.net.DatagramPacket;
import java.util.ArrayList;

import game.util.GameServerLogger;
import game.controller.GameInstance;
import game.packet.*;
import util.Utilities;

/**
 * GameServerThread.java  Date Created: Oct 30, 2012
 *
 * Purpose: This is the lobby.server thread that will listen for client requests.
 *
 * Description:  This is where all the magic happens.  When a client makes
 * a request it comes here and gets handled according to what it received.
 * Anything from creating a game lobby, to sending chat, to changing a game option
 * will be handled in this thread.
 *
 * @author Chrisb
 */
public class GameServerThread extends Thread {
    //Objects used by the lobby.server
    protected DatagramSocket socket;
    protected InetAddress gameAddress;
    protected final int gamePort = 2022;
    protected InetAddress address;
    protected int port;
    protected ArrayList<GameInstance> gameList;
    protected byte[] incoming;
    protected byte[] outgoing;
    protected boolean gameExists;
    protected boolean userExists;

    /*
       Contructors
     */
    public GameServerThread() throws IOException {
        this("GameServerThread");
    }

    public GameServerThread(String name) throws IOException {
        super(name);
        socket = new DatagramSocket(2024);
        gameList = new ArrayList<GameInstance>();
    }

    /**
     * Magic happens here..
     * Every request made comes to this method to be handled.
     */
    public void run() {
        while (true) {
            try {
                System.out.println("running...");
                //Reset all relavent variables
                incoming = new byte[256];
                outgoing = new byte[256];
                gameExists = false;
                userExists = false;
                gameAddress = null;

                //Receive a lobby.packet
                DatagramPacket packet = new DatagramPacket(incoming, incoming.length);
                socket.receive(packet);

                System.out.println("gameServer packet received");

                //Obtain port and address of the sender.
                address = packet.getAddress();
                port = packet.getPort();

                if (incoming[0] == OpCode.START_GAME.valueOf()) {
                    /*
                     * A new game has just been started.  Add it to the list of currently active games.
                     */
                    System.out.println("game.server START_GAME");
                    StartGamePacket sgp = new StartGamePacket(incoming);
                    gameList.ensureCapacity(gameList.size() + 1);
                    GameInstance newGame = new GameInstance(sgp.getLobbyId(), sgp.getOptions(), sgp.getMSD());
                    gameList.add(newGame);
                    
//                    gameAddress = Utilities.idAsInetAddress(sgp.getLobbyId());
//                    socket.send(new DatagramPacket(incoming, incoming.length, gameAddress, gamePort));
                    //END
                } else if (incoming[0] == OpCode.JOIN.valueOf()) {
                    /*
                     * A game has been started, add these users to the game.
                     */
                    System.out.println("game.server JOIN");
                    JoinLobbyPacket jlp = new JoinLobbyPacket(incoming);
                    for (GameInstance game : gameList)
                        if (Utilities.compareLobbyIds(game.getGameId(), jlp.getLobbyId()))
                            game.addUsers(jlp.getUsers());

                    //END
                } else if (incoming[0] == GameOps.GAME_INFO.valueOf()) {
                    /*
                     * A new game is being requested to start.  If lobby Id is not in use it can start.
                     */
                    System.out.println("game.server GAME_INFO");
                    GenericPacket gp = new GenericPacket(incoming);
                    gameAddress = Utilities.idAsInetAddress(gp.getGameId());

                    for (GameInstance game : gameList) {
                        if (Utilities.compareLobbyIds(gp.getGameId(), game.getGameId())) {
                            gameExists = true;
                            //Send Game Options
                            outgoing = new StartGamePacket(game.getGameId(), game.getAllOptions(), game.getMSD(), gp.getNation()).getPacket();
//                            DatagramPacket gameStartedPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                            DatagramPacket gameStartedPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                            socket.send(gameStartedPacket);
                        }
                    }
                    if (!gameExists) {
                        outgoing = new GameNotFoundPacket(gp.getGameId(), gp.getOpCode()).getPacket();
                        DatagramPacket gameNotFoundPacket = new DatagramPacket(outgoing, outgoing.length, address, port);
                        socket.send(gameNotFoundPacket);
                    }

                    //END
                } else if (incoming[0] == GameOps.GAME_USERS.valueOf()) {
                    /*
                     * Users of a game have been requested.  Send the list of users in game with matching game Id.
                     */
                    System.out.println("game.server GAME_USERS");
                    GenericPacket gp = new GenericPacket(incoming);
                    gameAddress = Utilities.idAsInetAddress(gp.getGameId());

                    for (GameInstance game : gameList) {
                        if (Utilities.compareLobbyIds(gp.getGameId(), game.getGameId())) {
                            gameExists = true;
                            game.setupGame();
                            //Send Users
                            if (!game.isGameFull()) {
                                //One Packet
                                outgoing = new JoinLobbyPacket(game.getGameId(), game.getUsers()).getPacket();
                                DatagramPacket addUsersPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                                socket.send(addUsersPacket);
                            } else {
                                //Two Packets
                                ArrayList<User> firstSixUsers = new ArrayList<User>(game.getUsers().subList(0,6));
                                outgoing = new JoinLobbyPacket(game.getGameId(), firstSixUsers).getPacket();
                                DatagramPacket firstSixPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                                socket.send(firstSixPacket);

                                outgoing = new JoinLobbyPacket(game.getGameId(), game.getUsers().get(6)).getPacket();
                                DatagramPacket lastUserPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                                socket.send(lastUserPacket);
                            }
                        }
                    }
                    if (!gameExists) {
                        outgoing = new GameNotFoundPacket(gp.getGameId(), gp.getOpCode()).getPacket();
                        DatagramPacket gameNotFoundPacket = new DatagramPacket(outgoing, outgoing.length, address, port);
                        socket.send(gameNotFoundPacket);
                    } else {
                        outgoing = new GameReadyPacket(gp.getGameId(), gp.getNation()).getPacket();
                        DatagramPacket gameReadyPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                        socket.send(gameReadyPacket);
                    }

                    //END
                } else if (incoming[0] == GameOps.ADD_UNITS.valueOf()) {
                    /*
                     * Units have been placed on the map.  Update the servers instance of the map.
                     */
                    System.out.println("game.server ADD_UNITS");
                    boolean unitsAdded = false;
                    GameAddUnitsPacket addUnits = new GameAddUnitsPacket(incoming);
                    gameAddress = Utilities.idAsInetAddress(addUnits.getGameId());

                    for (GameInstance game : gameList) {
                        if (Utilities.compareLobbyIds(addUnits.getGameId(), game.getGameId())) {
                            gameExists = true;
                            Region region = game.getMap().getRegionFromIndex(addUnits.getRegionIndex(), addUnits.getRegionType());
                            ArrayList<MilitaryUnit> units = Utilities.getUnitsFromPacket(addUnits);
                            unitsAdded = game.addUnitsToRegion(region.toString(), units);
                            if (!unitsAdded)
                                GameServerLogger.log("Unable to add units to region: " + region.toString() + " for nation: " + addUnits.getOwningNation());
                        }
                    }

                    if (!gameExists) {
                        outgoing = new GameNotFoundPacket(addUnits.getGameId(), addUnits.getOpCode()).getPacket();
                        DatagramPacket gameNotFoundPacket = new DatagramPacket(outgoing, outgoing.length, address, port);
                        socket.send(gameNotFoundPacket);
                    } else {
                        if (unitsAdded) {
                            outgoing = addUnits.getPacket();
                            DatagramPacket addUnitsPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                            socket.send(addUnitsPacket);
                        }
                    }

                    //END
                } else if (incoming[0] == GameOps.UNITS_ADDED.valueOf()) {
                    /*
                     * Adding units have been finished for a specific nation.
                     */
                    System.out.println("game.server UNITS_ADDED");
                    AllUnitsAddedPacket allUnitsAdded = new AllUnitsAddedPacket(incoming);
                    gameAddress = Utilities.idAsInetAddress(allUnitsAdded.getGameId());
                    outgoing = allUnitsAdded.getPacket();
                    socket.send(new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort));
                    //END
                } else if (incoming[0] == GameOps.CHAT.valueOf()) {
                    /*
                     * A game chat packet has been received.  If it is valid send it to all clients in game.
                     */
                    System.out.println("game.server CHAT");
                    GameChatPacket gcp = new GameChatPacket(incoming);

                    for (GameInstance game : gameList)
                        if (Utilities.compareLobbyIds(gcp.getGameId(), game.getGameId()))
                            gameExists = true;

                    if (gameExists) {
                        gameAddress = Utilities.idAsInetAddress(gcp.getGameId());

                        outgoing = gcp.getPacket();
                        DatagramPacket chatPacket = new DatagramPacket(outgoing, outgoing.length, gameAddress, gamePort);
                        socket.send(chatPacket);
                    }
                    //END
                }

            } catch (IOException e) {
                System.err.println("Game Server Thread IOerror.");
                e.printStackTrace();
                GameServerLogger.log("GameServerThread - " + e.getMessage());
                break;
            } catch (Exception e) {
                System.err.println("Game Server Thread has thrown an Error");
                e.printStackTrace();
                GameServerLogger.log("GameServerThread - " + e.getMessage());
            }
        }
    }
}